use crate::error::*;

#[allow(dead_code)]
#[derive(PartialEq, Eq)]
pub enum HttpResult {
    Complete,
    Partial,
}

// 读取到\r\n后返回该行的长度.
#[allow(dead_code)]
pub fn http_read_crlf_line(buf: &[u8]) -> Option<usize> {
    let mut res: usize = 0;
    let mut index = 0;
    let buf_len = buf.len();
    loop {
        if index >= buf_len {
            return None;
        }

        if buf[index] == b'\n' && index > 0 && buf[index - 1] == b'\r' {
            res += 1;
            return Some(res);
        }

        res += 1;
        index += 1;
    }
}

pub fn parse_http_header(buf: &[u8]) -> Result<(&[u8], &[u8])> {
    let len = buf.len();
    if len < 2 || buf[len - 2] != b'\r' || buf[len - 1] != b'\n' {
        return Error::e_explain(
            ErrorType::InvalidHttpHeader,
            "invalid http header",
        );
    }

    if len == 2 {
        // empty line
        return Ok((&buf[0..0], &buf[0..0]));
    }

    let mut off = 0;
    for (i, v) in buf.iter().enumerate().take(len).skip(off) {
        let ch = *v;

        if ch == b':' {
            off = i;
            break;
        }

        if ch.is_ascii_digit() {
            continue;
        }

        if ch == b'_' || ch == b'-' {
            continue;
        }

        let c = ch | 0x20;
        if c.is_ascii_lowercase() {
            continue;
        }

        return Error::e_explain(
            ErrorType::InvalidHttpHeader,
            "invalid http header",
        );
    }

    let header_name = &buf[0..off];

    if header_name.is_empty() {
        return Error::e_explain(
            ErrorType::InvalidHttpHeader,
            "invalid http header",
        );
    }

    // skip char ':'
    off += 1;

    // the last chars is "\r\n"
    let mut header_value = &buf[off..len - 2];
    header_value = header_value.trim_ascii();

    Ok((header_name, header_value))
}

// parse chunked size line, ignore any chunk-extension.
// For example,
//
//	"0" => "0"
//	"0;token" => "0"
//	"0;token=val" => "0"
//	`0;token="quoted string"` => "0"
pub fn http_parse_chunked_size(buf: &[u8]) -> Result<usize> {
    let len = buf.len();
    if len < 2 || buf[len - 2] != b'\r' || buf[len - 1] != b'\n' {
        return Error::e_explain(
            ErrorType::InvalidChunkedLine,
            "invalid chunked line",
        );
    }

    let buf = buf.trim_ascii_start();

    let mut res: usize = 0;
    for v in buf.iter().take(len - 2) {
        if *v == b';' || *v == b' ' {
            break;
        }

        if v.is_ascii_digit() {
            res = res * 16 + (*v - b'0') as usize;
            continue;
        }

        let ch = *v | 0x20;
        if (b'a'..=b'f').contains(&ch) {
            res = res * 16 + (ch - b'a' + 10) as usize;
            continue;
        }

        return Error::e_explain(
            ErrorType::InvalidChunkedLine,
            "invalid chunked line",
        );
    }

    Ok(res)
}

#[cfg(test)]
mod tests {
    use bytes::BytesMut;

    use super::*;

    #[test]
    fn test_http_read_line() {
        let buf = BytesMut::from("abc\r\nasadqw12321312312321331\r\n");
        let buf = buf.freeze();
        let res1 = http_read_crlf_line(&buf).unwrap();
        assert_eq!(res1, 5);
        let slice = buf.slice(0..res1);
        assert_eq!(slice, "abc\r\n".as_bytes());

        let slice = buf.slice(res1..);
        let res = http_read_crlf_line(&slice).unwrap();
        assert_eq!(slice, "asadqw12321312312321331\r\n".as_bytes());
        assert_eq!(res, "asadqw12321312312321331\r\n".len());

        let buf = BytesMut::from("abc");
        let buf = buf.freeze();
        let res = http_read_crlf_line(&buf);
        assert_eq!(res, None);
    }

    #[test]
    fn test_parse_http_header() {
        let res =
            parse_http_header("Test:  abcdef 123  \r\n".as_bytes()).unwrap();
        assert_eq!(res.0, "Test".as_bytes());
        assert_eq!(res.1, "abcdef 123".as_bytes());
    }

    #[test]
    fn test_parse_chunked_size() {
        let res = http_parse_chunked_size(b"123ABC\r\n").unwrap();
        assert!(res == 1194684);

        let res = http_parse_chunked_size(b"123abc\r\n").unwrap();
        assert!(res == 1194684);
        let res = http_parse_chunked_size(b"123abc;token\r\n").unwrap();
        assert!(res == 1194684);
    }
}
